/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;
import network.packets.AdResponce;
import network.packets.ChangeDriverStateFromServer;
import network.packets.ChangeDriverStateResponce;
import network.packets.DistrictChangedResponce;
import network.packets.DistrictStatisticResponce;
import network.packets.DistrictsListResponce;
import network.packets.DistrictsUpdateServerTimeResponce;
import network.packets.DriverInBlackList;
import network.packets.DriversQueueResponce;
import network.packets.LoginResponcePacket;
import network.packets.MessageResponce;
import network.packets.MessagesArchiveResponce;
import network.packets.MoveToDistrictResponce;
import network.packets.MyInfoResponce;
import network.packets.OrderCancel;
import network.packets.OrderChanged;
import network.packets.OrderDoneResponce;
import network.packets.OrderYourResponce;
import network.packets.OrdersListResponce;
import network.packets.Packet;
import network.packets.PingRequest;
import network.packets.ServerMessage;
import network.packets.SetOrderResponce;
import network.packets.StartOrderResponce;
import network.packets.TimerCorrectorResponce;
import utils.Utils;

/**
 *
 * @author Kvest
 */
public class Connection implements Runnable
{
    private static final long PING_TIMEOUT = 40000L;

    private static Connection connection = null;;
    private static final Object lock = new Object();
    
    public static Connection getConnection()
    {
        synchronized(lock)
        {
            if (connection == null)
            {
                connection = new Connection();
            }
        }
        
        return connection;
    }
    
    private SocketConnection sc;
    private InputStream is;
    private OutputStream os;
    private String host;
    private String secondHost;
    private volatile boolean z;//флаг основного цыкла
    
    //Слушатели
    private ConnectionErrorListener connectionErrorListener = null;
    private ConnectionNotifier connectionNotifier = null;
    private Hashtable packetNotifiers;

    private Timer pingTimer;
    private boolean sended;
    private byte[] pingData;

    private Connection()
    {   
        sc = null;
        is = null;
        os = null;
        z = false;
        
        packetNotifiers = new Hashtable();
        pingData = PacketBuilder.createPingResponce();
    }
    
    public void start(String host, String secondHost)//Функция запуска потока
    {
        if (isConnected())
        {
            stop();
        }
        
        this.host = host;
        this.secondHost = secondHost;
        Thread thread = new Thread(this);
        z = true;
        thread.start();
    }

    public void stop()//Функция остоновки потока
    {
        z = false;
        disconnect();
    }
    
    public void run()
    {
        while(z)
        {
            if (sc == null)
            {
                try
                {
                    boolean connected = false;
                    //Пытаемся подключится к гавному серверу
                    try
                    {
                        sc = (SocketConnection)Connector.open("socket://" + host, Connector.READ_WRITE, true);
                        try
                        {
                            sc.setSocketOption(SocketConnection.KEEPALIVE, 1);
                        }catch(Exception ex){};
                        is = sc.openInputStream();
                        os = sc.openOutputStream();
                        
                        connected = true;
                    }
                    catch(Exception ex)
                    {
                        if (is != null)
                        {
                            is.close();
                        }
                        is = null;
                        if (os != null) 
                        {
                            os.close();
                        }
                        os = null;
                    }
                     
                    if (!z)
                    {
                        return;
                    }
                    
                    //Подключаемся к резервному серверу(если не подключились)
                    if (!connected)
                    {
                        sc = (SocketConnection)Connector.open("socket://" + secondHost, Connector.READ_WRITE, true);
                        try
                        {
                            sc.setSocketOption(SocketConnection.KEEPALIVE, 1);
                        }catch(Exception ex){};
                        is = sc.openInputStream();
                        os = sc.openOutputStream();
                    }
                    
                    if (connectionNotifier != null)
                    {
                        connectionNotifier.connectionEstablished();
                    }
                    if (!z)
                    {
                        return;
                    }

                    startPingTimer();
                }
                catch(ConnectionNotFoundException cnfe)
                {
                    //ToDo Kvest
                    //Delete this
                    //TestForm.LOG += "cnfe=" + cnfe.getMessage() + "\n";
                    
                    disconnect();
                    notifyConnectionErrorListener(ConnectionErrorListener.CONNECTION_NOT_FOUND);
                    return;
                }
                catch(IOException ioe)
                {
                    //ToDo Kvest
                    //Delete this
                    //TestForm.LOG += "ioe=" + ioe.getMessage() + "\n";
                    
                    disconnect();
                    notifyConnectionErrorListener(ConnectionErrorListener.IO_EXCEPTION);
                    return;
                }
                catch(SecurityException se)
                {
                    //ToDo Kvest
                    //Delete this
                    //TestForm.LOG += "se=" + se.getMessage() + "\n";
                    
                    disconnect();
                    notifyConnectionErrorListener(ConnectionErrorListener.SECURITY_EXCEPTION);
                    return;
                }
                catch(Exception e)
                {
                    //ToDo Kvest
                    //Delete this
                    //TestForm.LOG += "e=" + e.getMessage() + "\n";
                    
                    disconnect();
                    notifyConnectionErrorListener(ConnectionErrorListener.EXCEPTION);
                    return;
                }
                
//                TimerTask task = new TimerTask()
//                {
//                    public void run()
//                    {
//                        System.out.println("TimerTask");
//                        DriversQueueResponce ch = new DriversQueueResponce();
//                        ch.print();
//                        packetNotify(ch);
//                    }
//                };
//                Timer t = new Timer();
//                t.schedule(task, 5000, 5000);
            }
            
            //Читаем данные
            try
            {
                //Длинна пакета
                byte[] buff;
                buff = new byte[4];
                for (int i=0;i<4;i++)
                {
                    buff[i] = (byte)is.read();                    
                }
                
                //Определяем размер
                int size = Utils.byteToInt(buff);
System.out.println("size=" + size);
                buff = new byte[size];
                //Читаем данные
                //ToDo Kvest
                //Убрать переменную read
                int read = is.read(buff, 0, size);

                int reread = read;
                while (read < size && is.available() > 0 && reread != -1)   
                {
                    reread = is.read(buff,read, size - read);
                    read += reread;
                }

                if (read == size)
                {
                    System.out.print("[");
                    for (int i = 0; i < size; ++i)
                    {
                        System.out.print(buff[i] + ", ");
                    }
                    System.out.println("]");
                    
                    //Парсим пакет
                    Packet packet = parsePacket(buff);
                    
                    //Отдаем на обработку
                    packetNotify(packet);
                }
                else
                {
                    //ToDo Kvest
                    //Delete this
                    //TestForm.LOG += "WRONG_PACKET_SIZE: " + read + "!=" + size + " av=" + is.available() + "\n";
                    
                    disconnect();
                    notifyConnectionErrorListener(ConnectionErrorListener.WRONG_PACKET_SIZE);
                    return;
                }
            }
            catch(IOException ioe)
            {     
                //ToDo Kvest
                //Delete this
                //TestForm.LOG += "ioe=" + ioe.getMessage() + "\n";
                    
                disconnect();
                notifyConnectionErrorListener(ConnectionErrorListener.IO_EXCEPTION);
                return;
            }
            catch(Exception e)
            {     
                //ToDo Kvest
                //Delete this
                //TestForm.LOG += "e=" + e.getMessage() + "\n";
                    
                disconnect();
                notifyConnectionErrorListener(ConnectionErrorListener.EXCEPTION);
                return;
            }           
            Thread.yield();
        }
    }
     
    private Packet parsePacket(byte[] data)
    {
        byte[] idData = new byte[2];
        idData[0] = data[0];
        idData[1] = data[1];
        int id = Utils.byteToInt(idData);
        Packet result;
        switch (id)
        {
            case Packet.LOGIN_RESPONCE: result = new LoginResponcePacket(data); break;
            case Packet.DISTRICTS_UPDATE_SERVER_TIME_RESPONCE : result = new DistrictsUpdateServerTimeResponce(data); break;
            case Packet.DISTRICTS_LIST_RESPONCE : result = new DistrictsListResponce(data); break;
            case Packet.MOVE_TODISTRICT_RESPONCE : result = new MoveToDistrictResponce(data); break;
            case Packet.ORDERS_LIST_RESPONCE : result = new OrdersListResponce(data); break; 
            case Packet.ORDER_CHANGED : result = new OrderChanged(data); break;
            case Packet.ORDER_YOUR_RESPONCE : result = new OrderYourResponce(data); break; 
            case Packet.START_ORDER_RESPONCE : result = new StartOrderResponce(data); break; 
            case Packet.CHANGE_DRIVER_STATE_RESPONCE : result = new ChangeDriverStateResponce(data); break;
            case Packet.CHANGE_DRIVER_STATE_FROM_SERVER : result = new ChangeDriverStateFromServer(data); break;
            case Packet.DISTRICTS_STATISTIC_RESPONCE : result = new DistrictStatisticResponce(data); break;
            case Packet.ORDER_DONE : result = new OrderDoneResponce(data); break;
            case Packet.DISTRICT_CHANGED : result = new DistrictChangedResponce(data); break;
            case Packet.PING_REQUEST: result = new PingRequest(data); break;
            case Packet.DRIVERS_QUEUE_RESPONCE: result = new DriversQueueResponce(data); break;
            case Packet.AD_RESPONCE: result = new AdResponce(data); break;
            case Packet.MY_INFO_RESPONCE: result = new MyInfoResponce(data); break;
            case Packet.SERVER_MESSAGE_RESPONCE: result = new ServerMessage(data); break;
            case Packet.ORDER_CANCEL_RESPONCE: result = new OrderCancel(data); break;
            case Packet.DRIVER_IN_BLACK_LIST_RESPONCE: result = new DriverInBlackList(data); break; 
            case Packet.SET_ORDER_RESPONCE: result = new SetOrderResponce(data); break;
            case Packet.MESSAGE_RESPONCE: result = new MessageResponce(data); break;
            case Packet.TIMER_CORRECTOR_RESPONCE: result = new TimerCorrectorResponce(data); break;
            case Packet.MESSAGES_ARCHIVE_RESPONCE: result = new MessagesArchiveResponce(data); break;    
            default : result = null; 
        }
        if (result != null)
        {
            result.print();
        }
        
        return result;
    }
    
    private void packetNotify(Packet packet)
    {
        if (packet == null)
        {
            return;
        }
        
        Integer packetId = new Integer(packet.getId());
     
        if (packetNotifiers.containsKey(packetId))
        {
            ((PacketNotifier)packetNotifiers.get(packetId)).incomingPacket(packet);
        }
    }
    
    public synchronized boolean send(byte[] buff)//Функция отправки данных по сети
    {
        try
        {
            if (os != null)//Если исходяший поток созданн,то отправляем данные
            {
                os.write(buff);
                os.flush();
                sended = true;
                return true;
            }
            else//Если не создан - возвращаем false
            {
                return false;
            }
        }
        catch(IOException ioe)
        {
            //ToDo Kvest
            //Delete this
            //TestForm.LOG += "send ioe=" + ioe.getMessage() + "\n";
            
            return false;
        }
    }
    
    public void disconnect()//Функция разрыва соединения
    {
        stopPingTimer();
        
        try
        {
            if (os != null)
            {
                os.close();
            }
            os = null;
        }catch(Exception e){}
        try
        {
            if (is != null)
            {
                is.close();
            }
            is = null;
        }catch(Exception e){}
        try
        {
            if (sc != null)
            {
                sc.close();
            }
            sc = null;
        }catch(Exception e){}
    }

    private void startPingTimer()
    {
        stopPingTimer();

        sended = false;
        pingTimer = new Timer();
        pingTimer.schedule(new TimerTask()
        {
            public void run()
            {
                if (!sended)
                {
                    send(pingData);
                }
                sended = false;
            }
        },PING_TIMEOUT,PING_TIMEOUT);
    }

    private void stopPingTimer()
    {
        if (pingTimer != null)
        {
            pingTimer.cancel();
        }

        pingTimer = null;
    }

    public boolean isConnected()
    {
        return (sc != null);
    }
    
    public void setConnectionErrorListener(ConnectionErrorListener listener)
    {
        connectionErrorListener = listener;
    }
    
    private void notifyConnectionErrorListener(int code)
    {
        if (connectionErrorListener != null && z)
        {
            connectionErrorListener.connectionError(code);
        }
    }
    
    public void setPacketListener(int id, PacketNotifier listener)
    {
        packetNotifiers.put(new Integer(id), listener);
    }
    
    public void deletePacketListener(int id)
    {
        packetNotifiers.remove(new Integer(id));
    }
    
    public void setConnectionNotifier(ConnectionNotifier listener)
    {
        connectionNotifier = listener;
    }
    
    public interface ConnectionErrorListener
    {
        public static final int CONNECTION_NOT_FOUND = 1;
        public static final int IO_EXCEPTION = 2;
        public static final int SECURITY_EXCEPTION = 3;
        public static final int EXCEPTION = 4;
        public static final int WRONG_PACKET_SIZE = 5;
        
        public void connectionError(int code);
    }
    
    public interface ConnectionNotifier
    {
        public void connectionEstablished();
    }
    
    public interface PacketNotifier
    {
        public void incomingPacket(Packet packet);
    }
}
